<?php

declare(strict_types=1);

namespace DreamCat\StateMachine\Impl;

use DreamCat\StateMachine\Enums\StateHealthLevelEnum;
use DreamCat\StateMachine\Error\DefineConfigException;
use DreamCat\StateMachine\Error\HealthCheckError;
use DreamCat\StateMachine\Popo\Define\ImmediatelyTransfer;
use DreamCat\StateMachine\Popo\Define\MachineNodeDetail;
use DreamCat\StateMachine\Popo\Define\TransferDetail;
use DreamCat\StateMachine\Popo\Health\NextActorInfo;
use DreamCat\StateMachine\Popo\MachineData;
use DreamCat\StateMachine\Popo\MachineDefine;
use DreamCat\StateMachine\Popo\StateHealthResult;
use DreamCat\StateMachine\StateInterface\ActionExecConditionInterface;
use DreamCat\StateMachine\StateInterface\ActionStatusQueryInterface;
use DreamCat\StateMachine\StateInterface\PreConditionInterface;
use DreamCat\StateMachine\StateMachineProviderInterface;
use DreamCat\StateMachine\StateMachineTransferInterface;
use Throwable;

/**
 * 状态机转移操作类
 * @author vijay
 */
class StateMachineTransfer implements StateMachineTransferInterface
{
    /**
     * @param StateMachineProviderInterface $stateMachineProvider
     */
    public function __construct(private StateMachineProviderInterface $stateMachineProvider)
    {
    }

    /** @inheritDoc */
    public function tryGoto(MachineData $machineData, string $targetState): void
    {
    }

    /** @inheritDoc */
    public function health(MachineData $machineData): StateHealthResult
    {
        $result = new StateHealthResult();
        $result->setMachineId($machineData->getMachineId())
            ->setState($machineData->getState())
            ->setTravelMark($machineData->getTravelMark());
        # 取出状态对应的配置
        $machineDefine = $this->stateMachineProvider->define();
        try {
            $nodeDetail = $this->getNode($machineDefine, $machineData->getState());
            foreach ($nodeDetail->getTransferList() as $transferDetail) {
                $result->addNextActorInfo($this->getNextActorInfo($machineData, $machineDefine, $transferDetail));
            }
            $result->setHealthLevel(StateHealthLevelEnum::HEALTH());
        } catch (HealthCheckError $exception) {
            $result->setHealthLevel($exception->getStateHealthLevel())
                ->setMessage($exception->getMessage());
        } catch (Throwable $exception) {
            $result->setMessage($exception->getMessage())
                ->setHealthLevel(StateHealthLevelEnum::FATAL());
        }
        # 调用对应处理器的方法
        return $result;
    }

    /**
     * 获取节点详情
     * @param MachineDefine $machineDefine 状态机定义
     * @param string $state 状态标识
     * @return MachineNodeDetail 节点详情
     * @throws HealthCheckError
     */
    private function getNode(MachineDefine $machineDefine, string $state): MachineNodeDetail
    {
        $node = $machineDefine->getNode($state);
        if (!$node) {
            throw new HealthCheckError(StateHealthLevelEnum::FATAL(), "找不到状态节点 {$state} 的定义");
        }
        return $node;
    }

    /**
     * 获取下一动作解析
     * @param MachineDefine $machineDefine 状态机定义
     * @param TransferDetail $transferDetail 转移详情
     * @return NextActorInfo 下一动作情况
     */
    private function getNextActorInfo(
        MachineData $machineData,
        MachineDefine $machineDefine,
        TransferDetail $transferDetail
    ): NextActorInfo {
        $result = new NextActorInfo();
        $result->setMainStream($transferDetail->isMainStream());
        if (is_a($transferDetail, ImmediatelyTransfer::class)) {
            $target = $transferDetail->getTarget();
        } else {
            $target = "";
        }
        try {
            $action = $machineDefine->getAction($transferDetail->getActionId());
            if (!$action) {
                $result->setActionName("unknown");
                throw new DefineConfigException("不存在的动作标识 {$transferDetail->getActionId()}");
            }
            $result->setActionName($action->getName())
                ->setOperatorList($action->getOperatorList());
            if ($action->getPrecondition()) {
                /** @var PreConditionInterface $preCondition */
                $preCondition = $this->stateMachineProvider->container()->get($action->getPrecondition());
                $preResult = $preCondition->canTransfer(
                    $machineData,
                    $target,
                    $transferDetail->getActionId()
                );
                $result->setPreConditionFailResult($preResult);
            }
            if (!$result->getPreConditionFailResult() && $action->getStatusQuery()) {
                /** @var ActionStatusQueryInterface $actionStatusQuery */
                $actionStatusQuery = $this->stateMachineProvider->container()->get($action->getStatusQuery());
                $status = $actionStatusQuery->transferQueryStatus($machineData, $transferDetail->getActionId());
                $result->setActionStatus($status);
            }
            if ($action->getExecCondition()) {
                /** @var ActionExecConditionInterface $execCondition */
                $execCondition = $this->stateMachineProvider->container()->get($action->getExecCondition());
                $execResult = $execCondition->canExecAction(
                    $machineData,
                    $target,
                    $transferDetail->getActionId()
                );
                $result->setActionExecConditionFail($execResult);
            }
        } catch (Throwable $exception) {
            $result->setException($exception);
        }
        return $result;
    }
}

# end of file
